package com.code.app.fix;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.BaseDexClassLoader;

public class FixDexManager {

    private final String TAG = "FixDexManager";

    private Context context;

    private File mDexDir;

    public FixDexManager(Context context) {
        this.context = context;
        //获取应用可以访问的dex目录
        this.mDexDir = context.getDir("odex", Context.MODE_PRIVATE);
    }

    /**
     * 修复
     *
     * @param fixDexPath 已经下载好的补丁dex
     * @throws Exception
     */
    public void fixDex(String fixDexPath) throws Exception {
        //移动到系统可以访问的dex目录
        File srcFile = new File(fixDexPath);
        if (!srcFile.exists()) {
            throw new ClassNotFoundException(fixDexPath);
        }
        File desttFile = new File(this.mDexDir, srcFile.getName());
        if (desttFile.exists()) {
            Log.d(TAG, fixDexPath + " 已经加载");
            return;
        }
        copyFile(srcFile, desttFile);

        //ClassLoader读取fixDex路径
        List<File> fixDexFiles = new ArrayList();
        fixDexFiles.add(desttFile);

        fixDexFile(fixDexFiles);
    }

    /**
     * 把dexElement注入到已运行classLoader中
     *
     * @param applicationgClassLoader
     * @param applicationDexElements
     * @throws Exception
     */
    private void injectDexElements(ClassLoader applicationgClassLoader, Object applicationDexElements) throws Exception {
        Field pathListField = BaseDexClassLoader.class.getField("pathList");
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(applicationgClassLoader);

        Class<?> pathListClass = pathList.getClass();
        Field dexElementsField = pathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        dexElementsField.set(pathList, applicationDexElements);
    }


    /**
     * 获取classLoader中的DexElement
     *
     * @param appClassLoader
     * @return
     * @throws Exception
     */
    private Object getDexElementByClassLoader(ClassLoader appClassLoader) throws Exception {
        // 获取pathList
        Field pathListField = BaseDexClassLoader.class.getField("pathList");
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(appClassLoader);

        //pathList里面的dexElement
        Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        return dexElementsField.get(pathList);
    }

    /**
     * 加载全部补丁包
     */
    public void loadFixDex() throws Exception {
        File[] dexFiles = mDexDir.listFiles();
        List<File> fixDexs = new ArrayList<>();
        for (File file : dexFiles) {
            if (file.getName().endsWith(".dex")) {
                fixDexs.add(file);
            }
        }
        fixDexFile(fixDexs);
    }

    /**
     * 开始修复
     *
     * @param fixDexFiles
     * @throws Exception
     */
    private void fixDexFile(List<File> fixDexFiles) throws Exception {
        //获取运行中的dexElement
        ClassLoader applicationgClassLoader = this.context.getClassLoader();
        Object applicationDexElements = getDexElementByClassLoader(applicationgClassLoader);

        File optimizedDirectory = new File(mDexDir, "odex");
        if (!optimizedDirectory.exists()) {
            optimizedDirectory.mkdirs();
        }

        for (File fixDexFile : fixDexFiles) {
            //dexPath dex路径
            //optimizedDirectory 解压路基
            //librarySearchPath so库路径
            ClassLoader fixDexClassLoader = new BaseDexClassLoader(
                    fixDexFile.getAbsolutePath(),
                    optimizedDirectory,
                    null,
                    applicationgClassLoader);

            Object fixDexElements = getDexElementByClassLoader(fixDexClassLoader);
            //把补丁的dexElement插到已经运行的dexElement的前面 合并
            applicationDexElements = combineArray(fixDexElements, applicationDexElements);
        }

        //把dexElement注入到已运行classLoader中
        injectDexElements(applicationgClassLoader, applicationDexElements);
    }

    /**
     * 文件复制
     *
     * @param srcFile
     * @param destFile
     */
    private void copyFile(File srcFile, File destFile) {
        try {
            FileInputStream fileInputStream = new FileInputStream(srcFile);
            FileOutputStream fileOutputStream = new FileOutputStream(destFile);
            byte[] buffer = new byte[1024];
            int byteRead;
            while (-1 != (byteRead = fileInputStream.read(buffer))) {
                fileOutputStream.write(buffer, 0, byteRead);
            }
            fileInputStream.close();
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "文件复制失败");
        }

    }

    /**
     * 合并两个dexElements数组
     *
     * @param arrayLhs
     * @param arrayRhs
     * @return
     */
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class<?> localClass = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);
        int j = i + Array.getLength(arrayRhs);
        Object result = Array.newInstance(localClass, j);
        for (int k = 0; k < j; ++k) {
            if (k < i) {
                Array.set(result, k, Array.get(arrayLhs, k));
            } else {
                Array.set(result, k, Array.get(arrayRhs, k - i));
            }
        }
        return result;
    }

}
